package account

import (
	"context"
	"gf_blog_admin/internal/consts"
	"gf_blog_admin/internal/dao"
	"gf_blog_admin/internal/model"
	"gf_blog_admin/internal/model/entity"
	"gf_blog_admin/internal/service"
	"gf_blog_admin/utility/casbinPolicy"
	"gf_blog_admin/utility/crypto"
	"gf_blog_admin/utility/errors"
	"gf_blog_admin/utility/utils"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/os/gtime"
)

type sAuth struct{}

func init() {
	service.RegisterAuth(NewAuth())
}

func NewAuth() *sAuth {
	return &sAuth{}
}

func (s *sAuth) Login(ctx context.Context, in *model.AuthLoginInPut) (tokenString string, expires int64, err error) {
	user, err := s.getUser(ctx, in.Username, in.Password)
	if err != nil {
		return
	}

	// 获取角色
	roles, err := casbinPolicy.Enforcer.GetRolesForUser(user.Username)
	if err != nil {
		return
	}

	// 生成token
	claims := &model.AdminClaims{
		UserId:   user.Id,
		Username: user.Username,
		Nickname: user.Nickname,
		Avatar:   user.Avatar,
		IsSuper:  user.Username == consts.SuperAdmin,
		Role:     roles[0],
		Device:   "pc",
		LoginIp:  utils.GetClientIp(ctx),
		LoginAt:  gtime.Now(),
	}
	tokenString, expires, err = service.Token().GenerateToken(ctx, claims)
	if err != nil {
		err = gerror.New("Failed to generate token")
		return
	}

	// 记录日志
	err = dao.BlSysUser.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		if err = s.recordUserLoginInfo(ctx, claims); err != nil {
			return err
		}
		if err = s.addLoginLog(ctx, claims, 1, "账号登录成功"); err != nil {
			return err
		}
		return nil
	})
	return
}

func (s *sAuth) Logout(ctx context.Context) (err error) {
	user := ctx.Value(consts.CtxUser).(*model.JwtClaims)
	err = service.Token().JoinBlack(ctx, "")
	if err = s.addLoginLog(ctx, user.AdminClaims, 3, "账号登出成功"); err != nil {
		return err
	}
	return
}

// 通过账号密码获取用户
func (s *sAuth) getUser(ctx context.Context, username, password string) (user *entity.BlSysUser, err error) {
	cls := dao.BlSysUser.Columns()
	err = dao.BlSysUser.Ctx(ctx).
		Where(cls.Username, username).
		Where(cls.UserStatus, 1).
		Scan(&user)
	if err != nil {
		return nil, err
	}
	if user == nil || user.PasswordHash == "" {
		return nil, gerror.NewCode(errors.CodeUserPassInvalid)
	}
	if !crypto.PasswordVerify(password, user.PasswordHash) {
		return nil, gerror.NewCode(errors.CodeUserPassInvalid)
	}
	return
}

// 更新用户在线信息
func (s *sAuth) recordUserLoginInfo(ctx context.Context, user *model.AdminClaims) (err error) {
	uCls := dao.BlSysUserInfo.Columns()
	_, err = dao.BlSysUserInfo.Ctx(ctx).Data(map[string]interface{}{
		uCls.UserId:       user.UserId,
		uCls.LastLoginIp:  user.LoginIp,
		uCls.LastActiveAt: user.LoginAt,
	}).Save()
	return
}

// 记录登录日志
func (s *sAuth) addLoginLog(ctx context.Context, user *model.AdminClaims, logType int, msg string) (err error) {
	var (
		lCls = dao.BlSysLoginLog.Columns()
		ua   = utils.UserAgent(ctx)
	)

	_, err = dao.BlSysLoginLog.Ctx(ctx).Data(map[string]interface{}{
		lCls.UserId:     user.UserId,
		lCls.LogType:    logType,
		lCls.LogRemark:  msg,
		lCls.Platform:   ua.OS + " " + ua.OSVersion,
		lCls.Browser:    ua.Name + " " + ua.Version,
		lCls.DeviceInfo: ua.Device,
		lCls.IpAddress:  user.LoginIp,
	}).Insert()
	return
}
